package models

import (
	"context"
	"crypto/ecdsa"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/gin-gonic/gin"
	"github.com/mr-tron/base58/base58"
	"go-chain-api/global"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo/options"
	"math"
	"math/big"
	"net/http"
	"strconv"
)

type UserPrivateKey struct {
	Id              string  `bson:"_id,omitempty" json:"id"`
	UserId          string  `bson:"userId" json:"userId"`
	MainCoinType    int64   `bson:"mainCoinType" json:"mainCoinType"`       // udun的id
	FeeAddress      string  `bson:"feeAddress" json:"feeAddress"`           // 手续费地址（私钥）
	CollectAddress  string  `bson:"collectAddress" json:"collectAddress"`   // 归集地址
	WithdrawAddress string  `bson:"withdrawAddress" json:"withdrawAddress"` // 提币地址（私钥）
	ChainName       string  `bson:"chainName" json:"chainName"`
	MinAmount       float64 `bson:"minAmount" json:"minAmount"` // 最小归集金额
}

type AddressInfo struct {
	Id              string `bson:"_id,omitempty" json:"id"`
	MainCoinType    int64  `bson:"mainCoinType" json:"mainCoinType"`
	FeeAddress      string `bson:"feeAddress" json:"feeAddress"`
	CollectAddress  string `bson:"collectAddress" json:"collectAddress"`
	WithdrawAddress string `bson:"withdrawAddress" json:"withdrawAddress"`
	ChainName       string `bson:"chainName" json:"chainName"`
	Balance         string `bson:"balance" json:"balance"`
}

type AddressFee struct {
	FeeAddress        string     `bson:"feeAddress" json:"feeAddress"`
	FeeBalance        string     `bson:"feeBalance" json:"feeBalance"`
	WithdrawAddress   string     `bson:"withdrawAddress" json:"withdrawAddress"`
	WithdrawBalance   []TokenFee `bson:"withdrawBalance" json:"withdrawBalance"`
	CollectionAddress string     `bson:"collectionAddress" json:"collectionAddress"`
	CollectionFee     []TokenFee `bson:"collectionFee" json:"collectionFee"`
}

type TokenFee struct {
	Name    string `bson:"name" json:"name"`
	Balance string `bson:"balance" json:"balance"`
}

func (u UserPrivateKey) Create() error {
	client := global.DBEngine.Database(global.DbConfig.DbName).Collection("user_private_key")
	MinAmountStr := fmt.Sprintf("%.1f", u.MinAmount)
	u.MinAmount, _ = strconv.ParseFloat(MinAmountStr, 64)

	filter := bson.M{
		"userId":          u.UserId,
		"mainCoinType":    u.MainCoinType,
		"feeAddress":      u.FeeAddress,
		"collectAddress":  u.CollectAddress,
		"withdrawAddress": u.WithdrawAddress,
		"minAmount":       u.MinAmount,
	}

	documents, err := client.CountDocuments(context.TODO(), filter)
	if err != nil {
		return err
	}
	if documents != 0 {
		return errors.New("data is exist")
	}
	_, err = client.InsertOne(context.TODO(), u)
	if err != nil {
		return err
	}
	return nil
}

func (u UserPrivateKey) GetListByPage(pageIndex uint, pageSize uint, c *gin.Context) ([]*AddressInfo, int64, error) {
	client := global.DBEngine.Database(global.DbConfig.DbName).Collection("user_private_key")
	findOptions := options.Find()
	findOptions.SetSkip(int64(pageIndex))
	findOptions.SetLimit(int64(pageSize))
	findOptions.SetSort(bson.D{{"_id", -1}})

	data, err := client.Find(context.TODO(), bson.M{"userId": u.UserId}, findOptions)
	if err != nil {
		return nil, 0, err
	}

	var addressInfoList []*AddressInfo

	for data.Next(context.Background()) {
		// 添加到userPrivateKey中
		var user AddressInfo
		err := data.Decode(&user)
		if err != nil {
			return nil, 0, err
		}

		config, err := GetInfoInfoByName(user.ChainName, user.MainCoinType)
		if err != nil {
			return nil, 0, err
		}

		// TRX地址特殊处理
		if user.MainCoinType == 648126 {
			user.FeeAddress, err = TRXPrivateToAddress(user.FeeAddress)
			if err != nil {
				return nil, 0, err
			}
			if user.WithdrawAddress != "" {
				user.WithdrawAddress, err = TRXPrivateToAddress(user.WithdrawAddress)
				if err != nil {
					return nil, 0, err
				}
			}
			fee, err := GetTrxAmount(user.FeeAddress, "_")
			if err != nil {
				return nil, 0, err
			}
			user.Balance = fee
		} else {
			user.FeeAddress, err = PrivateToAddress(user.FeeAddress)
			if err != nil {
				return nil, 0, err
			}
			if user.WithdrawAddress != "" {
				user.WithdrawAddress, err = PrivateToAddress(user.WithdrawAddress)
				if err != nil {
					return nil, 0, err
				}
			}
			client, err := ethclient.Dial(config.RpcUrl)
			if err != nil {
				user.Balance = "0"
			} else {
				balance, _ := client.BalanceAt(c, common.HexToAddress(user.FeeAddress), nil)
				ethBalance := new(big.Float).Quo(new(big.Float).SetInt(balance), big.NewFloat(1e18))
				ethBalanceStr := ethBalance.Text('f', 9)
				user.Balance = ethBalanceStr
			}
			defer client.Close()
		}

		addressInfoList = append(addressInfoList, &user)
	}
	return addressInfoList, int64(len(addressInfoList)), nil
}

type TrxToken struct {
	TokenID      string `json:"tokenId"`
	Balance      string `json:"balance"`
	TokenAbbr    string `json:"tokenAbbr"`
	TokenDecimal uint   `json:"tokenDecimal"`
}

type TrxAccount struct {
	WithPriceTokens []TrxToken `json:"withPriceTokens"`
}

func GetTrxAmount(address string, tokenId string) (string, error) {
	rpcUrl := "https://apilist.tronscanapi.com/api/accountv2?address="

	resp, err := http.Get(rpcUrl + address)
	if err != nil {
		fmt.Println("err1", err)
		return "", err
	}
	defer resp.Body.Close()

	var account TrxAccount
	if err := json.NewDecoder(resp.Body).Decode(&account); err != nil {
		fmt.Println("err2", err)
		return "", err
	}

	result := account.WithPriceTokens

	fee := "0"
	for _, token := range result {
		if token.TokenID == tokenId {
			if token.Balance != "0" {
				num, _ := strconv.ParseFloat(token.Balance, 64)
				denominator := math.Pow(10, float64(token.TokenDecimal))
				fee = strconv.FormatFloat(num/denominator, 'f', -1, 64)
				break
			}

		}
	}

	return fee, nil

}

func (u UserPrivateKey) Update() error {
	client := global.DBEngine.Database(global.DbConfig.DbName).Collection("user_private_key")
	//
	//documents, err := client.CountDocuments(context.TODO(), bson.M{"userId": u.UserId, "mainCoinType": u.MainCoinType, "feeAddress": u.FeeAddress, "collectAddress": u.CollectAddress, "withdrawAddress": u.WithdrawAddress})
	//if err != nil {
	//	return err
	//}
	//if documents != 0 {
	//	return errors.New("data is exist")
	//}

	update := bson.M{}
	if u.MinAmount != 0 {
		MinAmountStr := fmt.Sprintf("%.1f", u.MinAmount)
		u.MinAmount, _ = strconv.ParseFloat(MinAmountStr, 64)
		update["minAmount"] = u.MinAmount
	}

	if u.WithdrawAddress != "" {
		update["withdrawAddress"] = u.WithdrawAddress
	}
	if u.CollectAddress != "" {
		update["collectAddress"] = u.CollectAddress
	}
	if u.FeeAddress != "" {
		update["feeAddress"] = u.FeeAddress
	}

	objId, err := primitive.ObjectIDFromHex(u.Id)

	_, err = client.UpdateOne(context.TODO(), bson.M{"_id": objId}, bson.M{"$set": update})
	if err != nil {
		return err
	}
	return nil
}

func (u *UserPrivateKey) GetInfo() (*UserPrivateKey, error) {
	client := global.DBEngine.Database(global.DbConfig.DbName).Collection("user_private_key")
	objId, err := primitive.ObjectIDFromHex(u.Id)
	err = client.FindOne(context.TODO(), bson.M{"_id": objId}).Decode(&u)
	if err != nil {
		return nil, err
	}
	return u, nil

}

func (u UserPrivateKey) Delete() error {
	client := global.DBEngine.Database(global.DbConfig.DbName).Collection("user_private_key")
	objId, err := primitive.ObjectIDFromHex(u.Id)
	_, err = client.DeleteOne(context.TODO(), bson.M{"_id": objId, "userId": u.UserId})
	if err != nil {
		return err
	}
	return nil
}

func PrivateToAddress(privateKey string) (string, error) {

	priKey, err := crypto.HexToECDSA(privateKey)
	if err != nil {
		return "", err
	}
	//priKeyBytes := crypto.FromECDSA(priKey)
	//fmt.Printf("私钥为: %s\n", hex.EncodeToString(priKeyBytes))

	pubKey := priKey.Public().(*ecdsa.PublicKey)
	// 获取地址
	addr := crypto.PubkeyToAddress(*pubKey)

	return addr.String(), nil
}

func TRXPrivateToAddress(privateKey string) (string, error) {
	priKey, err := crypto.HexToECDSA(privateKey)
	if err != nil {
		return "", err
	}
	publicKey := priKey.Public()
	publicKeyECDSA, _ := publicKey.(*ecdsa.PublicKey)
	addrStr := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
	addrStr = "41" + addrStr[2:]
	addb, _ := hex.DecodeString(addrStr)
	firstHash := sha256.Sum256(addb)
	secondHash := sha256.Sum256(firstHash[:])
	secret := secondHash[:4]
	addb = append(addb, secret...)
	address := base58.Encode(addb)
	return address, nil
}
